using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text.RegularExpressions;
using ViScript.Core.Argument;
using ViScript.Core.Arguments;
using ViScript.Core.ParameterRules;
using ViScript.Core.ParsedSentence;
using ViScript.Core.Utils;

namespace ViScript.Core
{
    public interface ISentenceRule : ISentenceParser
    {
        void Execute(object[] arguments);
        void Accept(ISentenceRuleVisitor visitor);
    }

    public class SentenceRule : ISentenceRule
    {
        private readonly ITextFactory textFactory;
        private readonly IParameterRule parameterRule;
        private readonly object obj;
        private readonly MethodInfo method;
        private readonly Regex regex;

        public SentenceRule(object obj, MethodInfo method, Regex regex, ITextFactory textFactory,
                            IParameterRule parameterRule)
        {
            this.textFactory = textFactory;
            this.parameterRule = parameterRule;
            this.obj = obj;
            this.method = method;
            this.regex = regex;
        }

        public MethodInfo Method
        {
            get { return method; }
        }

        public IParsedSentence ParseSentence(ISentence sentence)
        {
            IArguments arguments = ParseArguments(sentence);
            return new NormalParsedSentence(sentence, this, arguments);
        }

        public virtual IArguments ParseArguments(ISentence sentence)
        {
            MatchedArgument[] matchArguments = MatchArguments(sentence.TrimmedContent);
            if (matchArguments == null)
            {
                return UnrecognizedArguments.UNRECOGNIZED_ARGUMENTS;
            }
            List<IArgument> args = new List<IArgument>();
            foreach (MatchedArgument matchArgument in matchArguments)
            {
                IArgument arg =
                    parameterRule.Parse(matchArgument.ParameterType, textFactory.CreateText(matchArgument.Argument),
                                        sentence);
                args.Add(arg);
            }
            return new NormalArguments(args.ToArray());
        }

        public virtual MatchedArgument[] MatchArguments(string trimmedContent)
        {
            Match match = regex.Match(trimmedContent);
            if (!match.Success)
            {
                return null;
            }
            List<MatchedArgument> matchedArguments = new List<MatchedArgument>();
            ParameterInfo[] parameters = method.GetParameters();
            foreach (ParameterInfo parameter in parameters)
            {
                string arg = match.Groups[parameter.Name].Value;
                matchedArguments.Add(new MatchedArgument(parameter.ParameterType, arg));
            }
            return matchedArguments.ToArray();
        }

        public void Execute(object[] arguments)
        {
            method.Invoke(obj, arguments);
        }

        public void Accept(ISentenceRuleVisitor visitor)
        {
            visitor.VisitNativeSentenceRule(this);
        }

        public class MatchedArgument
        {
            private readonly Type parameterType;
            private readonly string argument;

            public MatchedArgument(Type parameterType, string argument)
            {
                this.parameterType = parameterType;
                this.argument = argument;
            }

            public Type ParameterType
            {
                get { return parameterType; }
            }

            public string Argument
            {
                get { return argument; }
            }
        }

        public override string ToString()
        {
            string description = method.Name;
            description = description.Replace('_', ' ');
            foreach (ParameterInfo parameter in method.GetParameters())
            {
                description = description.Replace(parameter.Name, string.Format("[{0}]", parameter.Name));
            }
            return description;
        }
    }
}